﻿using System;
using System.Collections.Generic;
using System.Data.Common;
using System.Linq;
using System.Linq.Expressions;

namespace PKTools.Database
{
    partial class Sql
    {
        Dictionary<string, ObjectMapInfo> _objMap;

        void GetObjectMap<T>()
        {
            this._objMap = objectMaps.Get(typeof(T).AssemblyQualifiedName);
        }

        public object ExecuteScalar(string query, params object[] objs)
        {
            SetCommandText(query, objs);

            using (new Stoper("execute scalar", () => this.command.CommandText))
                return this.command.ExecuteScalar();
        }

        T ReadObject<T>(DbDataReader reader) where T : SqlObject, new()
        {
            var
                obj = new T();
            object
                value;

            for (int i = 0; i < reader.FieldCount; i++)
            {
                value = reader[i];
                _objMap[reader.GetName(i)].PI.SetValue(obj, ToNull(value));
            }

            obj.__Reset();

            return obj;
        }

        public T LoadObject<T>(string query, params object[] objs) where T : SqlObject, new()
        {
            Open();
            SetCommandText(query, objs);

            using (new Stoper("load object", () => this.command.CommandText))
            {
                GetObjectMap<T>();
                T
                    obj;

                using (var reader = this.command.ExecuteReader())
                {
                    if (!reader.Read())
                        return default(T);

                    obj = ReadObject<T>(reader);
                }

                obj.__Initialize();
                return obj;
            }
        }

        public List<T> LoadObjectList<T>(string query, params object[] objs) where T : SqlObject, new()
        {
            Open();
            SetCommandText(query, objs);

            using (new Stoper("load object list", () => this.command.CommandText))
            {
                GetObjectMap<T>();

                var
                    list = new List<T>();

                using (var reader = this.command.ExecuteReader())
                    while (reader.Read())
                        list.Add(ReadObject<T>(reader));

                list.ForEach(e => e.__Initialize());
                return list;
            }
        }

        public Dictionary<TKey, TValue> LoadDictionary<TKey, TValue>(string query, params object[] objs)
        {
            Open();
            SetCommandText(query, objs);

            var
                dict = new Dictionary<TKey, TValue>();

            using (new Stoper("load dictionary", () => this.command.CommandText))
            using (var reader = this.command.ExecuteReader())
                while (reader.Read())
                    dict.Add((TKey)reader[0], (TValue)ToNull(reader[1]));

            return dict;
        }

        public List<T> LoadList<T>(string query, params object[] objs)
        {
            Open();
            SetCommandText(query, objs);

            var
                list = new List<T>();

            using (new Stoper("load dictionary", () => this.command.CommandText))
            using (var reader = this.command.ExecuteReader())
                while (reader.Read())
                    list.Add((T)ToNull(reader[0]));

            return list;
        }

        public T LoadByPK<T>(params object[] objs) where T : SqlObject, new()
        {
            Clear();

            GetObjectMap<T>();

            return LoadObject<T>("SELECT {0} FROM {1} WHERE {2}",
                GetFieldQuery(),
                GetTableQuery<T>(),
                GetKeyValueQuery<T>((e, i) => objs[i]));
        }

        public List<T> LoadBy<T>(Expression<Func<T, bool>> expression, int top = -1) where T : SqlObject, new()
        {
            Clear();

            GetObjectMap<T>();

            return LoadObjectList<T>(GetTopSelectQuery(
                GetFieldQuery(),
                GetTableQuery<T>(),
                SqlExpressionVisitor.Query(
                    this._objMap.ToDictionary(e => e.Value.PI.Name, e => e.Key),
                    expression),
                top));
        }

        protected abstract string GetTopSelectQuery(string fieldsQuery, string tableQuery, string whereQuery, int top);

        public SqlDynamic LoadDynamic(string query, params object[] objs)
        {
            Open();
            SetCommandText(query, objs);

            using (new Stoper("load dynamic", () => this.command.CommandText))
            using (var reader = this.command.ExecuteReader())
            {
                if (!reader.Read())
                    return null;

                return new SqlDynamic(reader);
            }
        }

        public List<SqlDynamic> LoadDynamicList(string query, params object[] objs)
        {
            Open();
            SetCommandText(query, objs);

            using (new Stoper("load dynamic list", () => this.command.CommandText))
            {
                var
                    list = new List<SqlDynamic>();

                using (var reader = this.command.ExecuteReader())
                    while (reader.Read())
                        list.Add(new SqlDynamic(reader));

                return list;
            }
        }

        public List<T> LoadAll<T>() where T : SqlObject, new()
        {
            Clear();

            GetObjectMap<T>();

            return LoadObjectList<T>("SELECT {0} FROM {1}",
                GetFieldQuery(),
                GetTableQuery<T>());
        }
    }
}
